package net.gdface.bean.jdk;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import net.gdface.reflection.MethodUtils;

/**
 * based on org.apache.commons.beanutils.BeanIntrospectionData,支持类成员({@link Field})读写<br>
 * <p>
 * An internally used helper class for storing introspection information about a bean
 * class.
 * </p>
 * <p>
 * This class is used by {@link PropertyUtilsBean}. When accessing bean properties via
 * reflection information about the properties available and their types and access
 * methods must be present. {@code PropertyUtilsBean} stores this information in a cache
 * so that it can be accessed quickly. The cache stores instances of this class.
 * </p>
 * <p>
 * This class mainly stores information about the properties of a bean class. Per default,
 * this is contained in {@code PropertyDescriptor} objects. Some additional information
 * required by the {@code BeanUtils} library is also stored here.
 * </p>
 * @author guyadong
 * @since 2.7.0
 */
class BeanIntrospectionData {
    /** An array with property descriptors for the managed bean class. */
    private final PropertyDescriptor[] descriptors;

    /** A map for remembering the write method names for properties. */
    private final Map<String, String> writeMethodNames;

	private final Map<String, Integer> indexMap;

    /**
     * Creates a new instance of {@code BeanIntrospectionData} and initializes its
     * completely.
     *
     * @param descs the array with the descriptors of the available properties
     */
    public BeanIntrospectionData(PropertyDescriptor[] descs) {
        this(descs, setUpWriteMethodNames(descs));
    }

    /**
     * Creates a new instance of {@code BeanIntrospectionData} and allows setting the map
     * with write method names. This constructor is mainly used for testing purposes.
     *
     * @param descs the array with the descriptors of the available properties
     * @param writeMethNames the map with the names of write methods
     */
    BeanIntrospectionData(PropertyDescriptor[] descs, Map<String, String> writeMethNames) {
        descriptors = descs;
        indexMap = setIndexMap(descriptors);
        writeMethodNames = writeMethNames;
    }

    /**
     * Returns the array with property descriptors.
     *
     * @return the property descriptors for the associated bean class
     */
    public PropertyDescriptor[] getDescriptors() {
        return descriptors;
    }

    /**
     * Returns the {@code PropertyDescriptor} for the property with the specified name. If
     * this property is unknown, result is <b>null</b>.
     *
     * @param name the name of the property in question
     * @return the {@code PropertyDescriptor} for this property or <b>null</b>
     */
    public PropertyDescriptor getDescriptor(String name) {
        Integer index = indexMap.get(name);
        if(null != index){
        	return getDescriptors()[index];
        }
        return null;
    }

    /**
     * Returns the write method for the property determined by the given
     * {@code PropertyDescriptor}. This information is normally available in the
     * descriptor object itself. However, at least by the ORACLE implementation, the
     * method is stored as a {@code SoftReference}. If this reference has been freed by
     * the GC, it may be the case that the method cannot be obtained again. Then,
     * additional information stored in this object is necessary to obtain the method
     * again.
     *
     * @param beanCls the class of the affected bean
     * @param desc the {@code PropertyDescriptor} of the desired property
     * @return the write method for this property or <b>null</b> if there is none
     */
    public Method getWriteMethod(Class<?> beanCls, PropertyDescriptor desc) {
        Method method = desc.getWriteMethod();
        if (method == null) {
            String methodName = writeMethodNames.get(desc.getName());
            if (methodName != null) {
                method = MethodUtils.getAccessibleMethod(beanCls, methodName,
                        desc.getPropertyType());
                if (method != null) {
                    try {
                        desc.setWriteMethod(method);
                    } catch (IntrospectionException e) {
                        // ignore, in this case the method is not cached
                    }
                }
            }
        }

        return method;
    }

    /**
     * Initializes the map with the names of the write methods for the supported
     * properties. The method names - if defined - need to be stored separately because
     * they may get lost when the GC claims soft references used by the
     * {@code PropertyDescriptor} objects.
     *
     * @param descs the array with the descriptors of the available properties
     * @return the map with the names of write methods for properties
     */
    private static Map<String, String> setUpWriteMethodNames(PropertyDescriptor[] descs) {
        Map<String, String> methods = new HashMap<String, String>();
        for (PropertyDescriptor pd : descs) {
            Method method = pd.getWriteMethod();
            if (method != null) {
                methods.put(pd.getName(), method.getName());
            }
        }
        return methods;
    }
    private static Map<String, Integer> setIndexMap(PropertyDescriptor[] descs) {
    	HashMap<String, Integer> descriptors = new LinkedHashMap<>(descs.length);

    	for(int i=0;i<descs.length;++i){
    		descriptors.put(descs[i].getName(), i);
    	}
    	return descriptors;
    }
}
